package com.ironman.sailboat.mobile.computer.v2;



import com.ironman.sailboat.mobile.util.TSAGeoMagUtil;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * 1.7
 * 私有化xxxxOffline 方法
 * 修复锁对象
 * 优化计算粒度
 * 磁北向下取整
 */
public class TrainComputer {

    private final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);

    private final ScheduledFuture<?> scheduledFuture1;

    private final ScheduledFuture<?> scheduledFuture2;

    private final int offlineTimeInterval = 3000;

    /**
     * 通用训练数据
     */
    private final GeneralTrainData generalTrainData;

    /**
     * 真北阈值过滤器
     */
    private final ThresholdFilter headingTThresholdFilter;

    /**
     * 船速阈值过滤器
     */
    private final ThresholdFilter speedThresholdFilter;

    /**
     * VMG阈值过滤器
     */
    private final ThresholdFilter vmgThresholdFilter;

    /**
     * 船首向阈值过滤器
     */
    private final ThresholdFilter yawThresholdFilter;

    /**
     * 平衡X轴阈值过滤器
     */
    private final ThresholdFilter rolThresholdFilter;

    /**
     * 俯仰角Y轴阈值过滤器
     */
    private final ThresholdFilter pitchThresholdFilter;

    /**
     * 心率阈值过滤器
     */
    private final List<ThresholdFilter> hrThresholdFilterList;

    /**
     * GPS最近一次更新时间
     */
    private final AtomicReference<Date> gpsLast;
    /**
     * 教练艇风数据最近一次更新时间
     */
    private final AtomicReference<Date> windLast;
    /**
     * 姿态传感器最近一次更新时间
     */
    private final AtomicReference<Date> postureLast;
    /**
     * 心率带最近一次更新时间
     */
    private final List<AtomicReference<Date>> hrLastList;

    private final Consumer<GeneralTrainData> uiDataConsumer;

    private final BiConsumer<Date, GeneralTrainData> generalTrainDataConsumer;

    /**
     * @param mHmName               主舵手姓名
     * @param color                 船组颜色
     * @param speedValueRefresh     船速刷新阈值  cm/s
     * @param speedCountRefresh     船速数据量阈值
     * @param vmgValueRefresh       VMG刷新阈值  cm/s
     * @param vmgCountRefresh       VMG数据量阈值
     * @param rolValueRefresh       平衡刷新阈值
     * @param rolCountRefresh       平衡数据量阈值
     * @param rolBalanceDeviation   平衡数据量阈值
     * @param pitchValueRefresh     俯仰角刷新阈值
     * @param pitchCountRefresh     俯仰角数据量阈值
     * @param pitchBalanceDeviation 俯仰角数据量阈值
     * @param headingValueRefresh   航向刷新阈值
     * @param headingCountRefresh   航向数据量阈值
     * @param yawValueRefresh       船首向刷新阈值
     * @param yawCountRefresh       船首向数据量阈值
     * @param hrValueRefresh        心率刷新阈值
     * @param hrCountRefresh        心率数据量阈值
     * @param hrCount               监测运动员心率的数量
     */
    public TrainComputer(
            String mHmName, String color,
            double speedValueRefresh, int speedCountRefresh,
            double vmgValueRefresh, int vmgCountRefresh,
            int rolValueRefresh, int rolCountRefresh, int rolBalanceDeviation,
            int pitchValueRefresh, int pitchCountRefresh, int pitchBalanceDeviation,
            int headingValueRefresh, int headingCountRefresh,
            int yawValueRefresh, int yawCountRefresh,
            int hrValueRefresh, int hrCountRefresh, int hrCount,
            Function<Double, double[]> standardUFun, Function<Double, double[]> standardDFun,
            Consumer<GeneralTrainData> uiDataConsumer, BiConsumer<Date, GeneralTrainData> generalTrainDataConsumer) {

        this.generalTrainData = new GeneralTrainData(
                mHmName, color, IntStream.range(0, hrCount).mapToObj(n -> -1).collect(Collectors.toList()),
                rolBalanceDeviation,
                pitchBalanceDeviation,
                standardUFun, standardDFun
        );

        this.speedThresholdFilter = new ThresholdFilter(speedValueRefresh, speedCountRefresh);
        this.vmgThresholdFilter = new ThresholdFilter(vmgValueRefresh, vmgCountRefresh);
        this.rolThresholdFilter = new ThresholdFilter(rolValueRefresh * 1.0, rolCountRefresh);
        this.pitchThresholdFilter = new ThresholdFilter(pitchValueRefresh * 1.0, pitchCountRefresh);
        this.headingTThresholdFilter = new ThresholdFilter(headingValueRefresh * 1.0, headingCountRefresh);
        this.yawThresholdFilter = new ThresholdFilter(yawValueRefresh * 1.0, yawCountRefresh);
        this.hrThresholdFilterList = IntStream.range(0, hrCount).mapToObj(i -> new ThresholdFilter(hrValueRefresh * 1.0, hrCountRefresh)).collect(Collectors.toList());

        this.gpsLast = new AtomicReference<>(new Date());
        this.windLast = new AtomicReference<>(new Date());
        this.postureLast = new AtomicReference<>(new Date());
        this.hrLastList = IntStream.range(0, hrCount).mapToObj(n -> new AtomicReference<>(new Date())).collect(Collectors.toList());

        this.uiDataConsumer = uiDataConsumer;
        this.generalTrainDataConsumer = generalTrainDataConsumer;

        this.scheduledFuture1 = scheduledExecutorService.scheduleAtFixedRate(() -> {
            synchronized (generalTrainData) {
                this.generalTrainDataConsumer.accept(new Date(), generalTrainData);
            }
        }, 3, 1, TimeUnit.SECONDS);

        this.scheduledFuture2 = scheduledExecutorService.scheduleAtFixedRate(() -> {
            Date now = new Date();
            if (now.getTime() - gpsLast.get().getTime() > offlineTimeInterval) {
                this.gpsOffline();
                gpsLast.set(now);
            }
            if (now.getTime() - windLast.get().getTime() > offlineTimeInterval) {
                this.windOffline();
                windLast.set(now);
            }
            if (now.getTime() - postureLast.get().getTime() > offlineTimeInterval) {
                this.postureOffline();
                postureLast.set(now);
            }
            for (int i = 0; i < hrLastList.size(); i++) {
                if (now.getTime() - hrLastList.get(i).get().getTime() > offlineTimeInterval) {
                    this.hrOffline(i);
                    hrLastList.get(i).set(now);
                }
            }
        }, 1, 1, TimeUnit.SECONDS);
    }

    /**
     * 手机GPS
     *
     * @param longitude 经度  十进制
     * @param latitude  纬度  十进制
     * @param heading   航向  速度为0时，传null
     * @param speed     船速 cm/s
     */
    public void gps(Double longitude, Double latitude, Integer heading, Double speed) {
        Date now = new Date();
        this.gpsLast.set(now);
        synchronized (generalTrainData) {
            this.generalTrainData.setLongitude(BigDecimal.valueOf(longitude).setScale(6, RoundingMode.HALF_UP).doubleValue());
            this.generalTrainData.setLatitude(BigDecimal.valueOf(latitude).setScale(6, RoundingMode.HALF_UP).doubleValue());
            this.generalTrainData.setHeadingT(heading);
            this.generalTrainData.setSpeed(speed);

            this.generalTrainData.compute("gps");

            this.filter("gps");
        }
    }

    /**
     * GPS模块不推送数据或GPS无效时调用
     */
    private void gpsOffline() {
        synchronized (generalTrainData) {
            this.generalTrainData.setLongitude(null);
            this.generalTrainData.setLatitude(null);
            this.generalTrainData.setHeadingT(null);
            this.generalTrainData.setSpeed(null);

            this.generalTrainData.compute("gps");

            this.filter("gps");
        }
    }

    /**
     * 教练艇网关
     *
     * @param windSourceBoatCode  教练艇编号
     * @param windSpeed           矢量平均风速  cm/s
     * @param windDirection       矢量平均风向
     * @param customWindSpeed     自定义风速
     * @param customWindDirection 自定义风向
     */
    public void wind(String windSourceBoatCode, Double windSpeed, Integer windDirection, Double customWindSpeed, Integer customWindDirection) {
        this.windLast.set(new Date());
        synchronized (generalTrainData) {
            this.generalTrainData.setWindSourceBoatCode(windSourceBoatCode);
            this.generalTrainData.setWindSpeed(windSpeed);
            this.generalTrainData.setWindDirection(windDirection);

            this.generalTrainData.setCustomWindSpeed(customWindSpeed);
            this.generalTrainData.setCustomWindDirection(customWindDirection);

            this.generalTrainData.compute("wind");

            this.filter("vmg");
        }
    }

    /**
     * 教练艇网关主题【gateway/env/wind/{tenantCode}/+】订阅不到数据时调用
     */
    private void windOffline() {
        synchronized (generalTrainData) {
            this.generalTrainData.setWindSourceBoatCode(null);
            this.generalTrainData.setWindSpeed(null);
            this.generalTrainData.setWindDirection(null);

            this.generalTrainData.setCustomWindSpeed(null);
            this.generalTrainData.setCustomWindDirection(null);

            this.generalTrainData.compute("wind");

            this.filter("vmg");
        }
    }

    /**
     * 姿态传感器
     *
     * @param yaw     船首向  z轴
     * @param rol     平衡    x轴
     * @param pitch   俯仰角    y轴
     * @param luojing 罗经修正值
     */
    public void posture(Integer yaw, Integer rol, Integer pitch, Integer luojing) {
        this.postureLast.set(new Date());
        synchronized (generalTrainData) {
            /*
             * 转换为指北坐标
             * 实时船首向(yaw)
             *   当-180≤ Yaw＜90，yaw = 90 - Yaw；当90 ≤  Yaw＜ 180，yaw = 450 - Yaw；
             */
            if (-180 <= yaw && yaw < 90) {
                this.generalTrainData.setYaw((90 - yaw + (Optional.ofNullable(luojing).orElse(0))) % 360);
            } else {
                this.generalTrainData.setYaw((450 - yaw + (Optional.ofNullable(luojing).orElse(0))) % 360);
            }

            /*
             * 实时平衡rol，需要将所有原始值取反 rol = -rol
             */
            this.generalTrainData.setRol(-rol);

            /*
             * 俯仰角 pitch，需要将所有原始值取反 pitch = -pitch
             */
            this.generalTrainData.setPitch(-pitch);

            this.generalTrainData.compute("posture");

            this.filter("posture");
        }
    }

    /**
     * 姿态传感器离线时调用
     */
    private void postureOffline() {
        synchronized (generalTrainData) {
            this.generalTrainData.setYaw(null);
            this.generalTrainData.setRol(null);

            this.generalTrainData.compute("posture");

            this.filter("posture");
        }
    }

    /**
     * 舵手心率
     */
    public void hr(int index, int hr) {
        this.hrLastList.get(index).set(new Date());
        synchronized (generalTrainData) {
            this.generalTrainData.getHr().set(index, hr);

            this.filter("hr" + index);
        }
    }

    /**
     * 心率带离线时调用
     *
     * @param index 索引 主舵手为0，其他依次递增
     */
    private void hrOffline(int index) {
        synchronized (generalTrainData) {
            this.generalTrainData.getHr().set(index, -1);

            this.filter("hr" + index);
        }
    }

    /**
     * 过滤
     */
    private void filter(String dataType) {
        switch (dataType) {
            case "gps":
                headingTThresholdFilter.filterVal(Optional.ofNullable(generalTrainData.getHeadingT()).map(val -> val * 1.0).orElse(null), true, val -> {
                    Integer headingT = Optional.ofNullable(val).map(Double::intValue).orElse(null);
                    generalTrainData.setHeadingT(headingT);
                    if (Objects.nonNull(headingT)) {
                        Float declination = Optional.ofNullable(TSAGeoMagUtil.calDeclination(generalTrainData.getLatitude(), generalTrainData.getLongitude())).orElse(0.F);
                        this.generalTrainData.setHeadingM(BigDecimal.valueOf((headingT - declination) % 360).setScale(0, RoundingMode.HALF_DOWN).intValue());
                    } else {
                        this.generalTrainData.setHeadingM(null);
                    }
                });
                speedThresholdFilter.filterVal(generalTrainData.getSpeed(), generalTrainData::setSpeed);
                vmgThresholdFilter.filterVal(generalTrainData.getVmg(), generalTrainData::setVmg);
                break;
            case "vmg":
                vmgThresholdFilter.filterVal(generalTrainData.getVmg(), generalTrainData::setVmg);
                break;
            case "posture":
                yawThresholdFilter.filterVal(Optional.ofNullable(generalTrainData.getYaw()).map(val -> val * 1.0).orElse(null), val ->
                        generalTrainData.setYaw(Optional.ofNullable(val).map(Double::intValue).orElse(null)));
                rolThresholdFilter.filterVal(Optional.ofNullable(generalTrainData.getRol()).map(val -> val * 1.0).orElse(null), val ->
                        generalTrainData.setRol(Optional.ofNullable(val).map(Double::intValue).orElse(null)));
                pitchThresholdFilter.filterVal(Optional.ofNullable(generalTrainData.getPitch()).map(val -> val * 1.0).orElse(null), val ->
                        generalTrainData.setPitch(Optional.ofNullable(val).map(Double::intValue).orElse(null)));
                break;
            case "hr0":
                hrThresholdFilterList.get(0).filterVal(generalTrainData.getHr().get(0) * 1.0, val -> generalTrainData.getHr().set(0, val.intValue()));
                break;
            case "hr1":
                hrThresholdFilterList.get(1).filterVal(generalTrainData.getHr().get(1) * 1.0, val -> generalTrainData.getHr().set(1, val.intValue()));
                break;
            default:
                break;
        }

        if (Objects.nonNull(this.uiDataConsumer)) {
            this.uiDataConsumer.accept(generalTrainData);
        }
    }

    /**
     * 销毁线程
     */
    public void destroy() {
        this.scheduledFuture1.cancel(true);
        this.scheduledFuture2.cancel(true);
        this.scheduledExecutorService.shutdownNow();
    }
}
